﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Services;
using ControleRepresentante.Common;
using ControleRepresentante.Logic.ImpostoHandlers;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories;
using ControleRepresentante.Logic.Statistics;
using WebSite.Services.Contracts;
using Attribute = ControleRepresentante.Logic.Models.Attribute;

namespace WebSite.Services
{
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class SyncService : WebServiceBase
    {
        [WebMethod(EnableSession = true)]
        public List<ClienteContract> GetClientes()
        {
            return SearchService.GetClientes(RepositoryProvider);
        }

        [WebMethod(EnableSession = true)]
        public List<VendedorContract> GetVendedores()
        {
            return SearchService.GetVendedores(RepositoryProvider);
        }

        [WebMethod(EnableSession = true)]
        public List<TransportadoraContract> GetTransportadoras()
        {
            return SearchService.GetTransportadoras(RepositoryProvider);
        }

        [WebMethod(EnableSession = true)]
        public List<FabricaContract> GetJolitexFabricas()
        {
            return RepositoryProvider.GetRepository<IFabricaRepository>().GetJolitexFabricas()
                .Select(x => new FabricaContract
                {
                    Id = x.Id,
                    Nome = x.Nome
                })
                .ToList();
        }

        [WebMethod(EnableSession = true)]
        public List<string> FilterSynchronizedPedidos(List<string> pedidos)
        {
            return RepositoryProvider.GetRepository<IPedidoRepository>(new ImpostoProvider())
                .FilterSynchronizedPedidos(pedidos.ToArray())
                .ToList();
        }

        [WebMethod(EnableSession = true)]
        public List<int> FilterSynchronizedRelatorioVisitas(List<int> relatorioVisitas)
        {
            return RepositoryProvider.GetRepository<IRelatorioVisitaRepository>()
                .FilterSynchronizedRelatorioVisitas(relatorioVisitas.ToArray())
                .ToList();
        }

        [WebMethod(EnableSession = true)]
        public List<int> FilterSynchronizedClientes(List<int> clientes)
        {
            return RepositoryProvider.GetRepository<IClienteRepository>()
                .FilterSynchronizedClientes(clientes.ToArray())
                .ToList();
        }

        [WebMethod(EnableSession = true)]
        public List<int> FilterSynchronizedTransportadoras(List<int> transportadoras)
        {
            return RepositoryProvider.GetRepository<ITransportadoraRepository>()
                .FilterSynchronizedTransportadoras(transportadoras.ToArray())
                .ToList();
        }

        [WebMethod(EnableSession = true)]
        public void Synchronize(List<PedidoContract> pedidoContracts)
        {
            Dictionary<Guid, Cliente> clientes = RepositoryProvider.GetRepository<IClienteRepository>()
                .GetModelsByIds(pedidoContracts.Select(x => x.ClienteId).Distinct())
                .ToDictionary(x => x.Id, x => x);
            Dictionary<Guid, Fabrica> fabricas = RepositoryProvider.GetRepository<IFabricaRepository>()
                .GetModelsByIds(pedidoContracts.Select(x => x.FabricaId).Distinct())
                .ToDictionary(x => x.Id, x => x);
            Dictionary<Guid, Transportadora> transportadoras = RepositoryProvider.GetRepository<ITransportadoraRepository>()
                .GetModelsByIds(pedidoContracts.Select(x => x.TransportadoraId).Distinct())
                .ToDictionary(x => x.Id, x => x);
            Dictionary<Guid, Vendedor> vendedores = RepositoryProvider.GetRepository<IVendedorRepository>()
                .GetModelsByIds(pedidoContracts.Select(x => x.VendedorId).Distinct())
                .ToDictionary(x => x.Id, x => x);
            Dictionary<Guid, Attribute> attributes = RepositoryProvider.GetRepository<IAttributeRepository>()
                .GetModelsByIds(pedidoContracts.SelectMany(x => x.AttributeValues).Select(x => x.AttributeId).Distinct())
                .ToDictionary(x => x.Id, x => x);

            IPedidoRepository pedidoRepository = RepositoryProvider.GetRepository<IPedidoRepository>(new ImpostoProvider());

            Representante representante = pedidoRepository.GetRepresentante();

            IEnumerable<Pedido> pedidos = pedidoContracts.Select(pedidoContract =>
            {
                Pedido pedido = new Pedido
                {
                    CondicaoPagamento = pedidoContract.CondicaoPagamento,
                    Data = pedidoContract.Data,
                    Desconto = pedidoContract.Desconto,
                    Observacao = pedidoContract.Observacao,
                    PrazoEntrega = pedidoContract.PrazoEntrega,
                    Cliente = clientes[pedidoContract.ClienteId],
                    Fabrica = fabricas[pedidoContract.FabricaId],
                    TotalMercadoria = pedidoContract.TotalMercadoria,
                    ValorImposto = pedidoContract.ValorImposto,
                    TotalLiquido = pedidoContract.TotalLiquido,                    
                    ManualCalculatedValues = true
                };
                pedido.SetPedidoJolitex();
                pedido.PedidoJolitex.Sincronizado = true;
                pedido.PedidoJolitex.Codigo = pedidoContract.Numero;
                pedido.Transportadora = transportadoras[pedidoContract.TransportadoraId];
                pedido.Vendedor = vendedores[pedidoContract.VendedorId];
                pedido.Representante = representante;
                foreach (AttributeValueContract attributeValueContract in pedidoContract.AttributeValues)
                {
                    pedido.AddAttributeValue(new AttributeValue
                    {
                        Attribute = attributes[attributeValueContract.AttributeId],
                        Value = attributeValueContract.Value
                    });
                }
                foreach (ProdutoContract produtoContract in pedidoContract.Produtos)
                {
                    pedido.AddProduto(new Produto
                    {
                        Codigo = produtoContract.Codigo,
                        Descricao = produtoContract.Descricao,
                        PrecoUnitario = produtoContract.PrecoUnitario,
                        Quantidade = produtoContract.Quantidade
                    });
                }
                return pedido;
            });

            pedidoRepository.AddModels(pedidos.ToArray());
        }

        [WebMethod(EnableSession = true)]
        public void SynchronizeRelatorioVisitas(List<RelatorioVisitaContract> relatorioVisitaContracts)
        {
            Dictionary<Guid, Cliente> clientes = RepositoryProvider.GetRepository<IClienteRepository>()
                .GetModelsByIds(relatorioVisitaContracts.Select(x => x.ClienteId).Distinct())
                .ToDictionary(x => x.Id, x => x);
            Dictionary<Guid, Vendedor> vendedores = RepositoryProvider.GetRepository<IVendedorRepository>()
                .GetModelsByIds(relatorioVisitaContracts.Select(x => x.VendedorId).Distinct())
                .ToDictionary(x => x.Id, x => x);
            Dictionary<Guid, Attribute> attributes = RepositoryProvider.GetRepository<IAttributeRepository>()
                .GetModelsByIds(relatorioVisitaContracts.SelectMany(x => x.AttributeValues).Select(x => x.AttributeId).Distinct())
                .ToDictionary(x => x.Id, x => x);

            IRelatorioVisitaRepository relatorioVisitaRepository = RepositoryProvider.GetRepository<IRelatorioVisitaRepository>();

            Representante representante = relatorioVisitaRepository.GetRepresentante();

            IEnumerable<RelatorioVisita> relatorioVisitas = relatorioVisitaContracts.Select(relatorioVisitaContract =>
            {
                RelatorioVisita relatorioVisita = new RelatorioVisita
                {
                    Data = relatorioVisitaContract.Data,
                    Observacao = relatorioVisitaContract.Observacao,
                    Cliente = clientes[relatorioVisitaContract.ClienteId],
                };
                relatorioVisita.SetRelatorioVisitaJolitex();
                relatorioVisita.RelatorioVisitaJolitex.Sincronizado = true;
                relatorioVisita.RelatorioVisitaJolitex.Codigo = relatorioVisitaContract.Codigo;
                relatorioVisita.RelatorioVisitaJolitex.Ocorrencia = (RelatorioVisitaJolitexOcorrencia) relatorioVisitaContract.Ocorrencia;
                relatorioVisita.Vendedor = vendedores[relatorioVisitaContract.VendedorId];
                relatorioVisita.Representante = representante;
                foreach (AttributeValueContract attributeValueContract in relatorioVisitaContract.AttributeValues)
                {
                    relatorioVisita.AddAttributeValue(new AttributeValue
                    {
                        Attribute = attributes[attributeValueContract.AttributeId],
                        Value = attributeValueContract.Value
                    });
                }
                return relatorioVisita;
            });

            relatorioVisitaRepository.AddModels(relatorioVisitas.ToArray());
        }

        [WebMethod(EnableSession = true)]
        public ImpostoContract[] GetImpostos()
        {
            return new[]
            {
                new ImpostoContract
                {
                    DescricaoContains = JolitexImpostoHandler.DescricaoContains,
                    Imposto = JolitexImpostoHandler.Imposto
                } 
            };
        }

        [WebMethod(EnableSession = true)]
        public void SynchronizeClientes(List<FullClienteContract> clienteContracts)
        {
            IVendedorRepository vendedorRepository = RepositoryProvider.GetRepository<IVendedorRepository>();
            Dictionary<Guid, Vendedor> vendedores = vendedorRepository.GetModelsByIds(clienteContracts.Select(x => x.VendedorId).Where(x => x.HasValue).Cast<Guid>().Distinct())
                .ToDictionary(x => x.Id, x => x);

            IClienteRepository clienteRepository = RepositoryProvider.GetRepository<IClienteRepository>();
            Representante representante = clienteRepository.GetRepresentante();
            foreach (FullClienteContract x in clienteContracts)
            {
                Cliente cliente = new Cliente
                {
                    Id = x.Id,
                    CNPJ = x.CNPJ,
                    InscricaoEstadual = x.InscricaoEstadual,
                    NomeFantasia = x.NomeFantasia,
                };
                if (x.VendedorId.HasValue)
                {
                    cliente.Vendedor = vendedores[x.VendedorId.Value];
                }
                cliente.Representante = representante;
                cliente.SetClienteJolitex();
                cliente.ClienteJolitex.Codigo = x.Codigo;
                cliente.ClienteJolitex.Regiao = x.Regiao;
                Endereco endereco = new Endereco
                {
                    Bairro = x.Bairro,
                    CEP = x.CEP,
                    Cidade = x.Cidade,
                    Estado = x.Estado,
                    Numero = String.Empty,
                    Rua = x.Endereco,
                    Principal = true,
                    Tipo = EnderecoType.Comercial
                };
                cliente.AddEndereco(endereco);
                if (x.Bairro == x.BairroEntrega && x.CEP == x.CEPEntrega && x.Cidade == x.CidadeEntrega &&
                    x.Estado == x.EstadoEntrega)
                {
                    cliente.EnderecoEntrega = endereco;
                }
                else if (!String.IsNullOrWhiteSpace(x.EnderecoEntrega))
                {
                    Endereco enderecoEntrega = new Endereco
                    {
                        Bairro = x.BairroEntrega,
                        CEP = x.CEPEntrega,
                        Cidade = x.CidadeEntrega,
                        Estado = x.EstadoEntrega,
                        Numero = String.Empty,
                        Rua = x.EnderecoEntrega,
                        Tipo = EnderecoType.Comercial
                    };
                    cliente.AddEndereco(enderecoEntrega);
                    cliente.EnderecoEntrega = enderecoEntrega;
                }
                cliente.AddContato(new Contato
                {
                    Principal = true,
                    Tipo = ContatoType.TelefoneComercial,
                    Valor = x.Telefone
                });

                SyncUpdate(cliente, n => cliente.RazaoSocial = n, x.RazaoSocial, clienteRepository, ClienteError.DuplicatedRazaoSocial);
            }
        }

        [WebMethod(EnableSession = true)]
        public void SynchronizeCodigoClientes(List<CodigoClienteContract> codigos)
        {
            IClienteRepository clienteRepository = RepositoryProvider.GetRepository<IClienteRepository>();
            Dictionary<Guid, Cliente> clientes = clienteRepository.GetModelsByIds(codigos.Select(x => x.Id)).ToDictionary(x => x.Id, x => x);
            foreach (CodigoClienteContract codigoCliente in codigos)
            {
                Cliente cliente = clientes[codigoCliente.Id];
                if (cliente.ClienteJolitex == null)
                {
                    cliente.SetClienteJolitex();
                }
                cliente.ClienteJolitex.Codigo = codigoCliente.Codigo;
                clienteRepository.UpdateModel(cliente);
            }
        }

        [WebMethod(EnableSession = true)]
        public void SynchronizeCodigoTransportadoras(List<CodigoTransportadoraContract> codigos)
        {
            ITransportadoraRepository transportadoraRepository = RepositoryProvider.GetRepository<ITransportadoraRepository>();
            Dictionary<Guid, Transportadora> transportadoras = transportadoraRepository.GetModelsByIds(codigos.Select(x => x.Id)).ToDictionary(x => x.Id, x => x);
            foreach (CodigoTransportadoraContract codigoTransportadora in codigos)
            {
                Transportadora transportadora = transportadoras[codigoTransportadora.Id];
                if (transportadora.TransportadoraJolitex == null)
                {
                    transportadora.SetTransportadoraJolitex();
                }
                transportadora.TransportadoraJolitex.Codigo = codigoTransportadora.Codigo;
                transportadoraRepository.UpdateModel(transportadora);
            }
        }

        [WebMethod(EnableSession = true)]
        public List<RelatorioVisitaOcorrenciaContract> GetRelatorioVisitaOcorrencias()
        {
            return Enum.GetValues(typeof (RelatorioVisitaJolitexOcorrencia))
                .Cast<RelatorioVisitaJolitexOcorrencia>()
                .Select(x => new RelatorioVisitaOcorrenciaContract
                {
                    Id = (int) x,
                    Name = x.GetDescription()
                })
                .ToList();
        }

        [WebMethod(EnableSession = true)]
        public void SynchronizeTransportadoras(List<FullTransportadoraContract> transportadoraContracts)
        {
            ITransportadoraRepository transportadoraRepository = RepositoryProvider.GetRepository<ITransportadoraRepository>();
            Representante representante = transportadoraRepository.GetRepresentante();
            foreach (FullTransportadoraContract x in transportadoraContracts)
            {
                Transportadora transportadora = new Transportadora
                {
                    Id = x.Id,
                    Representante = representante
                };
                transportadora.SetTransportadoraJolitex();
                transportadora.TransportadoraJolitex.Codigo = x.Codigo;
                transportadora.AddEndereco(new Endereco
                {
                    Bairro = x.Bairro,
                    CEP = x.CEP,
                    Cidade = x.Cidade,
                    Estado = x.Estado,
                    Numero = String.Empty,
                    Rua = x.Endereco,
                    Principal = true,
                    Tipo = EnderecoType.Comercial
                });
                transportadora.AddContato(new Contato
                {
                    Principal = true,
                    Tipo = ContatoType.TelefoneComercial,
                    Valor = x.Telefone
                });

                SyncUpdate(transportadora, n => transportadora.Nome = n, x.RazaoSocial, transportadoraRepository, TransportadoraError.DuplicatedNome);
            }
        }

        private static void SyncUpdate<TModel, TStatistics, TError>(TModel model, Action<string> setNome, string nome, IRepository<TModel, TStatistics> repository, TError duplicatedError)
            where TModel : ModelBase 
            where TStatistics : IStatistics
        {
            bool shouldTryAgain = true;
            int i = 0;
            while (shouldTryAgain)
            {
                try
                {
                    setNome(i == 0 ? nome : nome + i);
                    repository.AddModel(model);
                    shouldTryAgain = false;
                }
                catch (RepositoryException<TError> ex)
                {
                    if (Equals(ex.Error, duplicatedError))
                    {
                        i++;
                    }
                    else
                    {
                        throw;
                    }
                }
            }
        }

        [WebMethod(EnableSession = true)]
        public List<AttributeContract> GetPeditoAttributes()
        {
            return GetAttributes(AttributeType.Pedido);
        }

        [WebMethod(EnableSession = true)]
        public List<AttributeContract> GetRelatorioVisitaAttributes()
        {
            return GetAttributes(AttributeType.RelatorioVisita);
        }

        private List<AttributeContract> GetAttributes(AttributeType attributeType)
        {
            IAttributeRepository attributeRepository = RepositoryProvider.GetRepository<IAttributeRepository>();
            IEnumerable<Attribute> attributes = attributeRepository.GetAttributesByType(attributeType);
            return attributes.Select(x => new AttributeContract{Id = x.Id, Name = x.Name}).ToList();
        }
    }
}